1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 import java.applet.Applet;
34 import java.awt.Graphics;
35 import java.awt.Color;
36 import java.awt.event.*;
37 import java.io.*;
38 import java.net.URL;
39
40
41
42
43 @SuppressWarnings("serial")
44 class FileFormatException extends Exception {
45
46 public FileFormatException(String s) {
47 super(s);
48 }
49 }
50
51
52
53 final class Model3D {
54
55 float vert[];
56 int tvert[];
57 int nvert, maxvert;
58 int con[];
59 int ncon, maxcon;
60 boolean transformed;
61 Matrix3D mat;
62 float xmin, xmax, ymin, ymax, zmin, zmax;
63
64 Model3D() {
65 mat = new Matrix3D();
66 mat.xrot(20);
67 mat.yrot(30);
68 }
69
70
71 Model3D(InputStream is) throws IOException, FileFormatException {
72 this();
73 StreamTokenizer st = new StreamTokenizer(
74 new BufferedReader(new InputStreamReader(is, "UTF-8")));
75 st.eolIsSignificant(true);
76 st.commentChar('#');
77 scan:
78 while (true) {
79 switch (st.nextToken()) {
80 default:
81 break scan;
82 case StreamTokenizer.TT_EOL:
83 break;
84 case StreamTokenizer.TT_WORD:
85 if ("v".equals(st.sval)) {
86 double x = 0, y = 0, z = 0;
87 if (st.nextToken() == StreamTokenizer.TT_NUMBER) {
88 x = st.nval;
89 if (st.nextToken() == StreamTokenizer.TT_NUMBER) {
90 y = st.nval;
91 if (st.nextToken() == StreamTokenizer.TT_NUMBER) {
92 z = st.nval;
93 }
94 }
95 }
96 addVert((float) x, (float) y, (float) z);
97 while (st.ttype != StreamTokenizer.TT_EOL && st.ttype
98 != StreamTokenizer.TT_EOF) {
99 st.nextToken();
100 }
101 } else if ("f".equals(st.sval) || "fo".equals(st.sval) || "l".
102 equals(st.sval)) {
103 int start = -1;
104 int prev = -1;
105 int n = -1;
106 while (true) {
107 if (st.nextToken() == StreamTokenizer.TT_NUMBER) {
108 n = (int) st.nval;
109 if (prev >= 0) {
110 add(prev - 1, n - 1);
111 }
112 if (start < 0) {
113 start = n;
114 }
115 prev = n;
116 } else if (st.ttype == '/') {
117 st.nextToken();
118 } else {
119 break;
120 }
121 }
122 if (start >= 0) {
123 add(start - 1, prev - 1);
124 }
125 if (st.ttype != StreamTokenizer.TT_EOL) {
126 break scan;
127 }
128 } else {
129 while (st.nextToken() != StreamTokenizer.TT_EOL
130 && st.ttype != StreamTokenizer.TT_EOF) {
131
132 }
133 }
134 }
135 }
136 is.close();
137 if (st.ttype != StreamTokenizer.TT_EOF) {
138 throw new FileFormatException(st.toString());
139 }
140 }
141
142
143 int addVert(float x, float y, float z) {
144 int i = nvert;
145 if (i >= maxvert) {
146 if (vert == null) {
147 maxvert = 100;
148 vert = new float[maxvert * 3];
149 } else {
150 maxvert *= 2;
151 float nv[] = new float[maxvert * 3];
152 System.arraycopy(vert, 0, nv, 0, vert.length);
153 vert = nv;
154 }
155 }
156 i *= 3;
157 vert[i] = x;
158 vert[i + 1] = y;
159 vert[i + 2] = z;
160 return nvert++;
161 }
162
163
164 void add(int p1, int p2) {
165 int i = ncon;
166 if (p1 >= nvert || p2 >= nvert) {
167 return;
168 }
169 if (i >= maxcon) {
170 if (con == null) {
171 maxcon = 100;
172 con = new int[maxcon];
173 } else {
174 maxcon *= 2;
175 int nv[] = new int[maxcon];
176 System.arraycopy(con, 0, nv, 0, con.length);
177 con = nv;
178 }
179 }
180 if (p1 > p2) {
181 int t = p1;
182 p1 = p2;
183 p2 = t;
184 }
185 con[i] = (p1 << 16) | p2;
186 ncon = i + 1;
187 }
188
189
190 void transform() {
191 if (transformed || nvert <= 0) {
192 return;
193 }
194 if (tvert == null || tvert.length < nvert * 3) {
195 tvert = new int[nvert * 3];
196 }
197 mat.transform(vert, tvert, nvert);
198 transformed = true;
199 }
200
201
202
203 private void quickSort(int a[], int left, int right) {
204 int leftIndex = left;
205 int rightIndex = right;
206 int partionElement;
207 if (right > left) {
208
209
210
211
212 partionElement = a[(left + right) / 2];
213
214
215 while (leftIndex <= rightIndex) {
216
217
218
219 while ((leftIndex < right) && (a[leftIndex] < partionElement)) {
220 ++leftIndex;
221 }
222
223
224
225
226 while ((rightIndex > left) && (a[rightIndex] > partionElement)) {
227 --rightIndex;
228 }
229
230
231 if (leftIndex <= rightIndex) {
232 swap(a, leftIndex, rightIndex);
233 ++leftIndex;
234 --rightIndex;
235 }
236 }
237
238
239
240
241 if (left < rightIndex) {
242 quickSort(a, left, rightIndex);
243 }
244
245
246
247
248 if (leftIndex < right) {
249 quickSort(a, leftIndex, right);
250 }
251
252 }
253 }
254
255 private void swap(int a[], int i, int j) {
256 int T;
257 T = a[i];
258 a[i] = a[j];
259 a[j] = T;
260 }
261
262
263 void compress() {
264 int limit = ncon;
265 int c[] = con;
266 quickSort(con, 0, ncon - 1);
267 int d = 0;
268 int pp1 = -1;
269 for (int i = 0; i < limit; i++) {
270 int p1 = c[i];
271 if (pp1 != p1) {
272 c[d] = p1;
273 d++;
274 }
275 pp1 = p1;
276 }
277 ncon = d;
278 }
279 static Color gr[];
280
281
282
283
284
285 void paint(Graphics g) {
286 if (vert == null || nvert <= 0) {
287 return;
288 }
289 transform();
290 if (gr == null) {
291 gr = new Color[16];
292 for (int i = 0; i < 16; i++) {
293 int grey = (int) (170 * (1 - Math.pow(i / 15.0, 2.3)));
294 gr[i] = new Color(grey, grey, grey);
295 }
296 }
297 int lg = 0;
298 int lim = ncon;
299 int c[] = con;
300 int v[] = tvert;
301 if (lim <= 0 || nvert <= 0) {
302 return;
303 }
304 for (int i = 0; i < lim; i++) {
305 int T = c[i];
306 int p1 = ((T >> 16) & 0xFFFF) * 3;
307 int p2 = (T & 0xFFFF) * 3;
308 int grey = v[p1 + 2] + v[p2 + 2];
309 if (grey < 0) {
310 grey = 0;
311 }
312 if (grey > 15) {
313 grey = 15;
314 }
315 if (grey != lg) {
316 lg = grey;
317 g.setColor(gr[grey]);
318 }
319 g.drawLine(v[p1], v[p1 + 1],
320 v[p2], v[p2 + 1]);
321 }
322 }
323
324
325 void findBB() {
326 if (nvert <= 0) {
327 return;
328 }
329 float v[] = vert;
330 float _xmin = v[0], _xmax = _xmin;
331 float _ymin = v[1], _ymax = _ymin;
332 float _zmin = v[2], _zmax = _zmin;
333 for (int i = nvert * 3; (i -= 3) > 0;) {
334 float x = v[i];
335 if (x < _xmin) {
336 _xmin = x;
337 }
338 if (x > _xmax) {
339 _xmax = x;
340 }
341 float y = v[i + 1];
342 if (y < _ymin) {
343 _ymin = y;
344 }
345 if (y > _ymax) {
346 _ymax = y;
347 }
348 float z = v[i + 2];
349 if (z < _zmin) {
350 _zmin = z;
351 }
352 if (z > _zmax) {
353 _zmax = z;
354 }
355 }
356 this.xmax = _xmax;
357 this.xmin = _xmin;
358 this.ymax = _ymax;
359 this.ymin = _ymin;
360 this.zmax = _zmax;
361 this.zmin = _zmin;
362 }
363 }
364
365
366
367 @SuppressWarnings("serial")
368 public class ThreeD extends Applet
369 implements Runnable, MouseListener, MouseMotionListener {
370
371 Model3D md;
372 boolean painted = true;
373 float xfac;
374 int prevx, prevy;
375 float scalefudge = 1;
376 Matrix3D amat = new Matrix3D(), tmat = new Matrix3D();
377 String mdname = null;
378 String message = null;
379
380 @Override
381 public void init() {
382 mdname = getParameter("model");
383 try {
384 scalefudge = Float.valueOf(getParameter("scale")).floatValue();
385 } catch (Exception ignored) {
386
387 }
388 amat.yrot(20);
389 amat.xrot(20);
390 if (mdname == null) {
391 mdname = "model.obj";
392 }
393 resize(getSize().width <= 20 ? 400 : getSize().width,
394 getSize().height <= 20 ? 400 : getSize().height);
395 addMouseListener(this);
396 addMouseMotionListener(this);
397 }
398
399 @Override
400 public void destroy() {
401 removeMouseListener(this);
402 removeMouseMotionListener(this);
403 }
404
405 @Override
406 public void run() {
407 InputStream is = null;
408 try {
409 Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
410 is = new URL(getDocumentBase(), mdname).openStream();
411 Model3D m = new Model3D(is);
412 md = m;
413 m.findBB();
414 m.compress();
415 float xw = m.xmax - m.xmin;
416 float yw = m.ymax - m.ymin;
417 float zw = m.zmax - m.zmin;
418 if (yw > xw) {
419 xw = yw;
420 }
421 if (zw > xw) {
422 xw = zw;
423 }
424 float f1 = getSize().width / xw;
425 float f2 = getSize().height / xw;
426 xfac = 0.7f * (f1 < f2 ? f1 : f2) * scalefudge;
427 } catch (Exception e) {
428 md = null;
429 message = e.toString();
430 }
431 try {
432 if (is != null) {
433 is.close();
434 }
435 } catch (Exception e) {
436 }
437 repaint();
438 }
439
440 @Override
441 public void start() {
442 if (md == null && message == null) {
443 new Thread(this).start();
444 }
445 }
446
447 @Override
448 public void stop() {
449 }
450
451 @Override
452 public void mouseClicked(MouseEvent e) {
453 }
454
455 @Override
456 public void mousePressed(MouseEvent e) {
457 prevx = e.getX();
458 prevy = e.getY();
459 e.consume();
460 }
461
462 @Override
463 public void mouseReleased(MouseEvent e) {
464 }
465
466 @Override
467 public void mouseEntered(MouseEvent e) {
468 }
469
470 @Override
471 public void mouseExited(MouseEvent e) {
472 }
473
474 @Override
475 public void mouseDragged(MouseEvent e) {
476 int x = e.getX();
477 int y = e.getY();
478
479 tmat.unit();
480 float xtheta = (prevy - y) * 360.0f / getSize().width;
481 float ytheta = (x - prevx) * 360.0f / getSize().height;
482 tmat.xrot(xtheta);
483 tmat.yrot(ytheta);
484 amat.mult(tmat);
485 if (painted) {
486 painted = false;
487 repaint();
488 }
489 prevx = x;
490 prevy = y;
491 e.consume();
492 }
493
494 @Override
495 public void mouseMoved(MouseEvent e) {
496 }
497
498 @Override
499 public void paint(Graphics g) {
500 if (md != null) {
501 md.mat.unit();
502 md.mat.translate(-(md.xmin + md.xmax) / 2,
503 -(md.ymin + md.ymax) / 2,
504 -(md.zmin + md.zmax) / 2);
505 md.mat.mult(amat);
506 md.mat.scale(xfac, -xfac, 16 * xfac / getSize().width);
507 md.mat.translate(getSize().width / 2, getSize().height / 2, 8);
508 md.transformed = false;
509 md.paint(g);
510 setPainted();
511 } else if (message != null) {
512 g.drawString("Error in model:", 3, 20);
513 g.drawString(message, 10, 40);
514 }
515 }
516
517 private synchronized void setPainted() {
518 painted = true;
519 notifyAll();
520 }
521
522 @Override
523 public String getAppletInfo() {
524 return "Title: ThreeD \nAuthor: James Gosling? \n"
525 + "An applet to put a 3D model into a page.";
526 }
527
528 @Override
529 public String[][] getParameterInfo() {
530 String[][] info = {
531 { "model", "path string", "The path to the model to be displayed." },
532 { "scale", "float", "The scale of the model. Default is 1." }
533 };
534 return info;
535 }
536 }